﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

using zijian666.AnyExtensions.Privates;

namespace zijian666.AnyExtensions;

/// <summary>
/// 表达式树扩展方法
/// </summary>
public static class ExpressionExtensions
{
#pragma warning disable IDE1006, IDE0052
    private static readonly Expression FALSE = Expression.Constant(false);
    private static readonly Expression TRUE = Expression.Constant(true);
    private static readonly Expression NULL = Expression.Constant(null);
#pragma warning restore IDE1006, IDE0052

    /// <summary>
    /// 将对象转为表达式树，如果是表达式树则不进行任何操作
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    private static Expression CastExpression(object value) => value as Expression ?? Expression.Constant(value);

    /// <summary>
    /// 访问属性，支持子属性，例如（propertyName = "Child.Name"）
    /// </summary>
    public static MemberExpression Property(this Expression instance, string propertyName)
    {
        if (string.IsNullOrWhiteSpace(propertyName))
        {
            throw new ArgumentException($"“{nameof(propertyName)}”不能为 null 或空白。", nameof(propertyName));
        }

        if (propertyName.Contains('.'))
        {
            var names = propertyName.Split('.');
            var property = Expression.PropertyOrField(instance, names[0].Trim());

            for (var i = 1; i < names.Length; i++)
            {
                var name = names[i].Trim();
                if (Nullable.GetUnderlyingType(property.Type) is not null && name != "Value")
                {
                    property = Expression.Property(property, "Value");
                }
                property = Expression.PropertyOrField(property, name);
            }
            return property;
        }
        else
        {
            return Expression.PropertyOrField(instance, propertyName.Trim());
        }
    }

    /// <summary>
    /// 使用新的参数代理表达树中的方法体
    /// </summary>
    public static Expression? BodyProxy<T>(this Expression<T>? expr, IList<ParameterExpression> parameters)
    {
        if (expr is null)
        {
            return null;
        }
        if (expr.Parameters.Count == parameters.Count)
        {
            if (expr.Parameters.IsSame(parameters))
            {
                return expr.Body;
            }
        }
        return new ParametersProxy(expr.Parameters, parameters).Visit(expr.Body);
    }

    /// <summary>
    /// 使用新的参数代理表达式
    /// </summary>
    public static T Proxy<T>(this T expr, ParameterExpression parameter)
        where T : Expression
    {
        var source = expr?.FindParameter() ?? throw new ArgumentException($"无法在表达式中找到参数", nameof(expr));
        if (source == parameter)
        {
            return expr;
        }
        return (T)new ParameterProxy(source, parameter).Visit(expr);
    }

    /// <summary>
    /// 根据当前表达式的类型将值转为常量表达式
    /// </summary>
    private static Expression Constant(this Expression expr, object? value)
    {
        if (value is not null && !expr.Type().IsInstanceOfType(value))
        {
            value = value.ChangeType(expr.Type());
        }
        return Expression.Constant(value, expr?.Type() ?? typeof(object));
    }

    /// <summary>
    /// 将表达式转为Quote表达式
    /// </summary>
    private static Expression Quote(this LambdaExpression expr) => Expression.Quote(expr);

    /// <summary>
    /// 尝试查找表达式中所使用的参数
    /// </summary>
    public static ParameterExpression? FindParameter(this Expression? expression)
    {
        if (expression is null)
        {
            return null;
        }
        if (expression is ParameterExpression property)
        {
            return property;
        }
        if (expression is MemberExpression member)
        {
            return member.Expression.FindParameter();
        }
        if (expression is UnaryExpression unary)
        {
            return unary.Operand.FindParameter();
        }
        if (expression is BinaryExpression binary)
        {
            return FindParameter(binary.Left) ?? FindParameter(binary.Right);
        }
        if (expression is LambdaExpression lambda)
        {
            return lambda.Parameters[0];
        }
        if (expression is MethodCallExpression methodCall)
        {
            var p = FindParameter(methodCall.Object);
            if (p is not null)
            {
                return p;
            }
            foreach (var arg in methodCall.Arguments)
            {
                p = FindParameter(arg);
                if (p is not null)
                {
                    return p;
                }
            }
        }
        return null;
    }

    /// <summary>
    /// 尝试获取表达式在操作中的类型
    /// </summary>
    public static Type Type(this Expression expression)
    {
        if (expression is LambdaExpression lambda)
        {
            return lambda.ReturnType;
        }
        return expression.Type;
    }

    /// <summary>
    /// 构建调用静态方法的表达式
    /// </summary>
    public static Expression Call(this MethodInfo method, params object[] arguments)
        => Expression.Call(method, arguments.Select(CastExpression));
    /// <summary>
    /// 构建调用静态方法的表达式
    /// </summary>
    public static Expression Call(this MethodInfo method, object arg0)
        => Expression.Call(method, CastExpression(arg0));
    /// <summary>
    /// 构建调用静态方法的表达式
    /// </summary>
    public static Expression Call(this MethodInfo method, object arg0, object arg1)
        => Expression.Call(method, CastExpression(arg0), CastExpression(arg1));
    /// <summary>
    /// 构建调用静态方法的表达式
    /// </summary>
    public static Expression Call(this MethodInfo method, object arg0, object arg1, object arg2)
        => Expression.Call(method, CastExpression(arg0), CastExpression(arg1), CastExpression(arg2));

    /// <summary>
    /// 构建调用实例方法的表达式
    /// </summary>
    public static Expression Call(this Expression instance, MethodInfo method, params object[] arguments)
        => Expression.Call(instance, method, arguments.Select(CastExpression));
    /// <summary>
    /// 构建调用实例方法的表达式
    /// </summary>
    public static Expression Call(this Expression instance, MethodInfo method, object arg0)
        => Expression.Call(instance, method, CastExpression(arg0));
    /// <summary>
    /// 构建调用实例方法的表达式
    /// </summary>
    public static Expression Call(this Expression instance, MethodInfo method, object arg0, object arg1)
        => Expression.Call(instance, method, CastExpression(arg0), CastExpression(arg1));
    /// <summary>
    /// 构建调用实例方法的表达式
    /// </summary>
    public static Expression Call(this Expression instance, MethodInfo method, object arg0, object arg1, object arg2)
        => Expression.Call(instance, method, CastExpression(arg0), CastExpression(arg1), CastExpression(arg2));

    /// <summary>
    /// 构建泛型委托表达式
    /// </summary>
    public static Expression<TDelegate> Lambda<TDelegate>(this Expression expr, params ParameterExpression[] parameters)
    {
        if (parameters is null || parameters.Length == 0)
        {
            var parameter = expr.FindParameter();
            if (parameter is not null)
            {
                return Expression.Lambda<TDelegate>(expr, parameter);
            }
        }
        return Expression.Lambda<TDelegate>(expr, parameters);
    }

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression GreaterThan(this Expression property, IComparable value)
        => value is null ? FALSE : Expression.GreaterThan(property, property.Constant(value));

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression GreaterThanOrEqual(this Expression property, IComparable value)
        => value is null ? FALSE : Expression.GreaterThanOrEqual(property, property.Constant(value));

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression LessThan(this Expression property, IComparable value)
        => value is null ? FALSE : Expression.LessThan(property, property.Constant(value));

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression LessThanOrEqual(this Expression property, IComparable value)
        => value is null ? FALSE : Expression.LessThanOrEqual(property, property.Constant(value));

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression Between(this Expression property, IComparable? minimum, IComparable? maximum)
    {
        if (minimum is null)
        {
            if (maximum is null)
            {
                return FALSE;
            }
            return Expression.LessThanOrEqual(property, property.Constant(maximum));
        }

        if (maximum is null)
        {
            return Expression.GreaterThanOrEqual(property, property.Constant(minimum));
        }

        return Expression.And(
            Expression.GreaterThanOrEqual(property, property.Constant(minimum)),
            Expression.LessThanOrEqual(property, property.Constant(maximum))
        );
    }

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression NotBetween(this Expression property, IComparable minimum, IComparable maximum)
    {
        if (minimum is null)
        {
            if (maximum is null)
            {
                return FALSE;
            }
            return Expression.GreaterThan(property, property.Constant(maximum));
        }

        if (maximum is null)
        {
            return Expression.LessThan(property, property.Constant(minimum));
        }

        return Expression.Or(
            Expression.LessThan(property, property.Constant(minimum)),
            Expression.GreaterThan(property, property.Constant(maximum))
        );
    }

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression Equal(this Expression property, IQueryable values)
    {
        var contains = Methods.Queryable_Contains.MakeGenericMethod(property.Type());
        var field = FieldWrapper(values);
        var value = Expression.Convert(field, values.GetType());
        return Expression.Call(contains, value, property);
    }

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression Equal(this Expression property, IEnumerable values)
    {
        if (values is string str)
        {
            return Equal(property, str);
        }
        if (values is IQueryable queryable)
        {
            return Equal(property, queryable);
        }
        var list = values.OfType<object>().Select(x => x.ChangeType(property.Type(), false)).Where(x => x is not null).Distinct().ToList();
        if (list.Count == 0)
        {
            return FALSE;
        }
        if (list.Count == 1)
        {
            return Equal(property, list[0]);
        }
        var array = Array.CreateInstance(property.Type(), list.Count);
        for (var i = 0; i < list.Count; i++)
        {
            array.SetValue(list[i], i);
        }
        var contains = Methods.Enumerable_Contains.MakeGenericMethod(property.Type());
        return Expression.Call(contains, Expression.Constant(array), property);
    }

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression Equal(this Expression property, object? value)
    {
        if (value is IEnumerable values and not string)
        {
            return Equal(property, values);
        }
        return Expression.Equal(property, property.Constant(value));
    }

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression NotEqual(this Expression property, IQueryable values)
    {
        var contains = Methods.Queryable_Contains.MakeGenericMethod(property.Type());
        var field = FieldWrapper<object>(values);
        var value = Expression.Convert(field, values.GetType());
        return Expression.Not(Expression.Call(contains, value, property));
    }

    /// <summary>
    /// 包装为字段表达式
    /// </summary>
    public static Expression FieldWrapper<T>(T value)
    {
        var obj = new TempClass<T>(value);
        return Expression.Field(Expression.Constant(obj), TempClass<T>.FIELD_INFO);
    }

    private readonly struct TempClass<T>(T value)
    {
        public static readonly FieldInfo FIELD_INFO = typeof(TempClass<T>).GetField("Value");
        public readonly T Value = value;
    }

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression NotEqual(this Expression property, IEnumerable values)
    {
        if (values is string str)
        {
            return NotEqual(property, str);
        }
        if (values is IQueryable queryable)
        {
            return NotEqual(property, queryable);
        }
        var list = values.OfType<object>().Select(x => x.ChangeType(property.Type(), false)).Where(x => x is not null).Distinct().ToList();
        if (list.Count == 0)
        {
            return FALSE;
        }
        if (list.Count == 1)
        {
            return NotEqual(property, list[0]);
        }
        var array = Array.CreateInstance(property.Type(), list.Count);
        for (var i = 0; i < list.Count; i++)
        {
            array.SetValue(list[i], i);
        }
        var contains = Methods.Enumerable_Contains.MakeGenericMethod(property.Type());
        return Expression.Not(Expression.Call(contains, Expression.Constant(array), property));
    }

    /// <summary>
    /// 构建比较表达式
    /// </summary>
    public static Expression NotEqual(this Expression property, object? value)
    {
        if (value is IEnumerable values and not string)
        {
            return NotEqual(property, values);
        }
        return Expression.NotEqual(property, property.Constant(value));
    }

    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    private static Expression StringOperation(this MethodInfo operation, Expression property, string value)
        => value is null ? FALSE : property.Call(operation, value);

    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    private static Expression StringOperation(this MethodInfo operation, Expression property, IEnumerable<string> values)
    {
        if (values is IQueryable<string> queryable)
        {
            return StringOperation(operation, property, queryable);
        }
        if (values is null)
        {
            return FALSE;
        }
        var list = values.DistinctWithoutNull();
        if (list.Count == 0)
        {
            return FALSE;
        }
        if (list.Count == 1)
        {
            return StringOperation(operation, property, list.First());
        }
        Expression? expr = null;
        foreach (var value in list)
        {
            if (expr is null)
            {
                expr = property.Call(operation, value);
            }
            else
            {
                expr = Expression.Or(expr, property.Call(operation, value));
            }
        }
        return expr!;
    }

    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    private static Expression StringOperation(this MethodInfo operation, Expression property, IQueryable<string> values)
    {
        if (values is null)
        {
            return FALSE;
        }
        var s = Expression.Parameter(typeof(string), "s");
        var contains = property.Call(operation, s);
        var lambda = contains.Lambda<Func<string, bool>>(s);
        var field = FieldWrapper(values);
        var value = Expression.Convert(field, values.GetType());
        var any = GenericHelper<string>.QueryableAny.Call(value, lambda.Quote());
        return any;
    }

    /// <summary>
    /// 构建字符串模糊匹配操作表达式
    /// </summary>
    public static Expression Like(this Expression property, string value)
        => Methods.String_Contains.StringOperation(property, value);
    /// <summary>
    /// 构建字符串模糊匹配操作表达式
    /// </summary>
    public static Expression Like(this Expression property, IEnumerable<string> values)
        => Methods.String_Contains.StringOperation(property, values);
    /// <summary>
    /// 构建字符串模糊匹配操作表达式
    /// </summary>
    public static Expression Like(this Expression property, IQueryable<string> values)
        => Methods.String_Contains.StringOperation(property, values);

    /// <summary>
    /// 构建字符串模糊匹配操作表达式
    /// </summary>
    public static Expression NotLike(this Expression property, string value)
        => Expression.Not(Like(property, value));
    /// <summary>
    /// 构建字符串模糊匹配操作表达式
    /// </summary>
    public static Expression NotLike(this Expression property, IEnumerable<string> values)
        => Expression.Not(Like(property, values));
    /// <summary>
    /// 构建字符串模糊匹配操作表达式
    /// </summary>
    public static Expression NotLike(this Expression property, IQueryable<string> values)
        => Expression.Not(Like(property, values));

    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression StartsWith(this Expression property, string value)
        => Methods.String_StartsWith.StringOperation(property, value);
    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression StartsWith(this Expression property, IEnumerable<string> value)
        => Methods.String_StartsWith.StringOperation(property, value);
    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression StartsWith(this Expression property, IQueryable<string> value)
        => Methods.String_StartsWith.StringOperation(property, value);

    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression NotStartsWith(this Expression property, string value)
        => Expression.Not(StartsWith(property, value));
    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression NotStartsWith(this Expression property, IEnumerable<string> value)
        => Expression.Not(StartsWith(property, value));
    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression NotStartsWith(this Expression property, IQueryable<string> value)
        => Expression.Not(StartsWith(property, value));

    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression EndsWith(this Expression property, string value)
        => Methods.String_EndsWith.StringOperation(property, value);
    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression EndsWith(this Expression property, IEnumerable<string> value)
        => Methods.String_EndsWith.StringOperation(property, value);
    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression EndsWith(this Expression property, IQueryable<string> value)
        => Methods.String_EndsWith.StringOperation(property, value);

    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression NotEndsWith(this Expression property, string value)
        => Expression.Not(EndsWith(property, value));
    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression NotEndsWith(this Expression property, IEnumerable<string> value)
        => Expression.Not(EndsWith(property, value));
    /// <summary>
    /// 构建字符串操作的比较表达式
    /// </summary>
    public static Expression NotEndsWith(this Expression property, IQueryable<string> value)
        => Expression.Not(EndsWith(property, value));

    /// <summary>
    /// 表达式参数代理
    /// </summary>
    private class ParameterProxy(ParameterExpression source, ParameterExpression proxy) : ExpressionVisitor
    {
        private readonly ParameterExpression _source = source ?? throw new ArgumentNullException(nameof(source));
        private readonly ParameterExpression _proxy = proxy ?? throw new ArgumentNullException(nameof(proxy));

        protected override Expression VisitParameter(ParameterExpression node)
        {
            return _source == node ? _proxy! : node;
        }
    }

    private class ParametersProxy(ReadOnlyCollection<ParameterExpression> sources, IList<ParameterExpression> proxies) : ExpressionVisitor
    {
        private readonly IList<ParameterExpression> _proxies = proxies ?? throw new ArgumentNullException(nameof(proxies));
        private readonly IList<ParameterExpression> _sources = sources ?? throw new ArgumentNullException(nameof(sources));

        protected override Expression VisitParameter(ParameterExpression node)
        {
            var index = _sources.IndexOf(node);
            return _proxies.ElementAtOrDefault(index) ?? node;
        }
    }
}